#include "defaults.h"
#include "nurbs.h"
#include "config.h"

#include "io.h"
#include "utils.h"
#include "primitivemanager.h"
#include "testoptimizer.h"

math::Vec3f eye, lookat, up;
int cur_button = -1;
int last_x, last_y;
int screenwidth = 1024;
int screenheight = 768;
bool pressedCTRL = false;
bool pressedALT = false;
bool pressedSHIFT = false;
bool pressedRB = false;
bool renderAxes = true;

VisualDebugger visualDebugger;

Config config;

PrimitiveManager* primitiveManager = 0;

template<class T>
T& Type(Primitive* primitive) {
	return *static_cast<T*>(primitive);
}

template<class T>
T& CurrentPrimitive() {
	return *static_cast<T*>(primitiveManager->GetCurrentPrimitive());
}

std::string GetPath(const std::string& file) {
	std::string str = config.dataPath;
	str += file;
	return str;
}

void initGL() {
	GLfloat mat_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
	GLfloat mat_shininess[] = {45.0f};

	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);

	GLfloat light_ambient0[] = { 0.5, 0.5, 0.5, 1.0 };
	GLfloat light_diffuse0[] = { 1.0, 1.0, 1.0, 1.0 };
	GLfloat light_specular0[] = { 1.0, 1.0, 1.0, 1.0 };
	GLfloat light_position0[] = { -2.0, 2.0, 1.0, 1.0 };

	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient0);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse0);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular0);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position0);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
	float lmodel_twoside[] = {GL_TRUE};
	glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);

	glClearColor(161.0f / 255.0f, 161.0f / 255.0f, 161.0f / 255.0f, 1.0f);

	glEnable(GL_DEPTH_TEST);

	glShadeModel(GL_SMOOTH);
	glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
}

void RenderGL() {
	const Vec3n clearColor = math::V3n(161.0f / 255.0f, 161.0f / 255.0f, 161.0f / 255.0f);
	glClearColor(clearColor[0], clearColor[1], clearColor[2], 1.0f);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLdouble)screenwidth / screenheight, 0.05, 100.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glEnable(GL_LIGHTING);
	gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]);

	primitiveManager->Render();

	if(pressedRB)
		visualDebugger.RenderOrbit(eye, lookat);

	visualDebugger.Render();

	if(renderAxes) {
		visualDebugger.RenderGrid();
		visualDebugger.RenderAxis(screenwidth, screenheight, eye, lookat, up);
	}

	if(glGetError() != GL_NO_ERROR)
		std::cerr << "Opengl error" << std::endl;
}

void display() {
	RenderGL();
	glutSwapBuffers();
}

void reshape(int w, int h) {
	screenwidth = w;
	screenheight = h;

	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void keyboard(unsigned char key, int x, int y) {
	switch(key) {
		case 'a':
			renderAxes = !renderAxes;
			break;
		case 'd':
			eye = config.eye;
			lookat = config.lookat;
			up = config.up;
			break;
		case 'c':
			std::cout << std::setprecision(16);
			PRINTVAR(eye);
			PRINTVAR(lookat);
			break;
		case 'x':
			visualDebugger.Clear();
			primitiveManager->Clear();
			break;
		case 27:
			exit(0);
			break;
	}	
	glutPostRedisplay();
}

void specialkeyboard(int key, int x, int y) {
	PrimitiveRenderer* pr = primitiveManager->GetCurrentPrimitiveRenderer();

	switch(key) {
		case GLUT_KEY_F5:
			if(pr) {
				pr->Update();
			}
			break;
	}
	glutPostRedisplay();
}

void mouse(int button, int state, int x, int y) {
	cur_button = button;

	pressedCTRL = glutGetModifiers() & GLUT_ACTIVE_CTRL ? true : false;
	pressedALT = glutGetModifiers() & GLUT_ACTIVE_ALT ? true : false;
	pressedSHIFT = glutGetModifiers() & GLUT_ACTIVE_SHIFT ? true : false;
	pressedRB = (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) ? true : false;

	if(state == GLUT_DOWN) {
		last_x = x;	
		last_y = y;
	}
}

void mousemotion(int x, int y) {
	int dx = x - last_x;
	int dy = y - last_y;
	if(!pressedALT) {
		float len, theta;
		math::Vec3f neye, neye2, tempeye;
		math::Vec3f f, r, u;

		switch(cur_button) {
			case GLUT_LEFT_BUTTON:
				if(!pressedSHIFT) {
					f = lookat - eye;

					len = length(f);
					normalize(f);

					len -= sqrtf(len) * dx * 0.03f;

					tempeye = eye;
					eye = lookat - (len * f);

					if (len < 0.1) {
						eye = tempeye;
					}
				}
				break;
			case GLUT_RIGHT_BUTTON:
				neye = eye - lookat;

				theta = -dx * 0.007;
				neye2[0] = (float)cos(theta)*neye[0] + (float)sin(theta)*neye[2];
				neye2[1] = neye[1];
				neye2[2] =-(float)sin(theta)*neye[0] + (float)cos(theta)*neye[2];		

				theta = -dy * 0.007f;

				f[0] = -neye2[0];
				f[1] = -neye2[1];
				f[2] = -neye2[2];

				u.Set(0.0f, 1.0f, 0.0f);

				r = cross(f, u);
				u = cross(r, f);
				len = length(f);

				normalize(f);
				normalize(u);

				neye[0] = len * (cosf(theta) * f[0] + sinf(theta) * u[0]);
				neye[1] = len * (cosf(theta) * f[1] + sinf(theta) * u[1]);
				neye[2] = len * (cosf(theta) * f[2] + sinf(theta) * u[2]);

				eye = lookat - neye;
				break;
		}
	}

	last_x = x;
	last_y = y;
	
	glutPostRedisplay();
}

int main(int argc, char** argv) {
	std::vector<std::string> argument;
	for(size_t i = 1; i < argc; i++)
		argument.push_back(argv[i]);
	
	config.Init("programconfig.xml");

	eye = config.eye;
	lookat = config.lookat;
	up = config.up;

	screenwidth = config.screenwidth;
	screenheight = config.screenheight;

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(screenwidth, screenheight);
	glutInitWindowPosition(1280 - screenwidth, 0);
	glutCreateWindow("test");

	GLenum err = glewInit();
	if(GLEW_OK != err) {
		std::cerr << "error: glewInit failed." << std::endl;
		return 0;
	}

	initGL();

	primitiveManager = PrimitiveManager::Instance();

  	for(size_t i = 0; i < argument.size(); i++) {
		Primitive* primitive = OpenPrimitive(GetPath(argument[i]));
		if(primitive) primitiveManager->AddPrimitive(primitive, argument[i]);
	}

	testOptimizer(argument);

	glutDisplayFunc(display);
	glutMotionFunc(mousemotion);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutSpecialFunc(specialkeyboard);
	glutMouseFunc(mouse);
	glutMainLoop();

	return 0;
}
